package com.foreveross.bsl.push.application.impl.channel;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.foreveross.bsl.common.utils.mapper.BeanMapper;
import com.foreveross.bsl.push.application.AppChannelConfigService;
import com.foreveross.bsl.push.application.PushModuleException;
import com.foreveross.bsl.push.application.impl.DsRouterHelper;
import com.foreveross.bsl.push.application.impl.DsRouterPushId;
import com.foreveross.bsl.push.domain.Message;
import com.foreveross.bsl.push.domain.entity.Push;
import com.foreveross.bsl.push.domain.entity.channel.AppChannelConfig;
import com.foreveross.bsl.push.repository.PushRepository;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

public abstract class BaseChannel implements PushChannelable {

	private final static Logger log = LoggerFactory.getLogger(BaseChannel.class);

	@Autowired
	private AppChannelConfigService appChannelConfigService;

	@Autowired
	private ChannelConfig channelConfig;

	@Inject
	private SendFeedbackable feedbacker;

	private final Set<SendListener> sendListeners = Sets.newHashSet();

	private int retryCountOnSendFailed = 3;

	private int intervalOnSendFailed = 3000;

	private final PushRepository getPushRepository(){
		return new Push().getEntityRepository(PushRepository.class);
	}

	@Override
	public SendReport send(Set<SendTarget> targets, Message msg) {
		SendReport sr = new SendReport();
		Stopwatch stopwatch = new Stopwatch();
		NormalSendExceptionDecision failDesision = new NormalSendExceptionDecision(feedbacker);
		for (SendTarget target : targets) {
			if(log.isDebugEnabled()){
				log.debug("发送到{}台目标设备", target.getDeviceIds().size());
			}
			Collection<String> pushIds = target.getPushIds();
			this.notifyForSending(new SendingEvent(new Date(), pushIds));
			try {
				if(log.isTraceEnabled()){
					stopwatch.reset().start();
				}
				this.doSend(target, msg);
				if(log.isTraceEnabled()){
					log.trace("doSend() cost {}ms with {} pushs", stopwatch.elapsed(TimeUnit.MILLISECONDS), pushIds.size());
				}
			} catch (Exception e) {
				String dsRouter=DsRouterHelper.getCurrentDsRouter();
				List<DsRouterPushId> drps=Lists.newArrayListWithExpectedSize(pushIds.size());
				for(String pid : pushIds){
					drps.add(new DsRouterPushId(pid, dsRouter));
				}
				failDesision.handleException(e, drps.toArray(new DsRouterPushId[0]));
			}
		}
		return sr;
	}

	@Override
	public boolean addSendListener(SendListener sendListener) {
		return sendListeners.add(sendListener);
	}

	@Override
	public boolean removeSendListener(SendListener sendListener) {
		return sendListeners.remove(sendListener);
	}

	protected void notifyForSending(final SendingEvent event) {
		this.getPushRepository().findAndUpdateToSendingByIds(event.getSendingTime(), event.getPushIds());
	}

	public void close() {
		log.info("开始关闭渠道:{}的所有连接...", this.getChannelId());
		try {
			doClose();
			log.info("关闭渠道:{}成功", this.getChannelId());
		} catch (Exception e) {
			log.error("关闭渠道失败", e);
		}
	}

	/**
	 * 发送到渠道端的消息只包含：title, requestId, messageType和extras中指定发送的字段, 消息的详细信息再通过接口拉取
	 * @param target
	 * @param msg
	 * @throws Exception
	 */
	protected abstract void doSend(final SendTarget target, final Message msg) throws Exception;

	protected abstract void doClose() throws Exception;

	public AppChannelConfigService getAppChannelConfigService() {
		return appChannelConfigService;
	}

	public void setAppChannelConfigService(AppChannelConfigService appChannelConfigService) {
		this.appChannelConfigService = appChannelConfigService;
	}

	public AppChannelConfig getAppChannelConfig(String appId) {
		AppChannelConfig cfg = BeanMapper.map(this.getAppChannelConfigService().getConfig(appId), AppChannelConfig.class);
		if (cfg == null) {
			throw new PushModuleException("应用：" + appId + "的推送配置还未设置");
		}
		return cfg;
	}

	public ChannelConfig getChannelConfig() {
		return this.channelConfig;
	}

	/**
	 * 获取channel/channel.properties配置的代理。
	 * 各渠道使用渠道代号前缀配置各自的代理，以apns为例，eg:
	 * apns.proxy.host=127.0.0.1<br/>
	 * apns.proxy.port=8580<br/>
	 * apns.proxy.type=HTTP #type有HTTP和SOCKS,默认为HTTP<br/>
	 * host不配置表示不使用代理
	 * @return proxy对象或null
	 */
	protected Proxy getProxy(){
		final ChannelConfig cfg=this.getChannelConfig();
		final String channelId=this.getChannelId();
		String proxyHost=cfg.getProperty(channelId+".proxy.host");
		if(StringUtils.isBlank(proxyHost)){
			return null;
		}

		proxyHost=proxyHost.trim();
		String strProxyType=cfg.getProperty(channelId+".proxy.type", "HTTP");
		Proxy.Type proxyType=Proxy.Type.HTTP;
		if(strProxyType!=null){
			strProxyType=strProxyType.toUpperCase().trim();
			if("HTTP".equals(strProxyType)){
				proxyType=Proxy.Type.HTTP;
			}
			else if("SOCKS".equals(strProxyType)){
				proxyType=Proxy.Type.SOCKS;
			}
		}
		int proxyPort=1080;
		try {
			proxyPort=Integer.parseInt(cfg.getProperty(channelId+".proxy.port"));
		} catch (NumberFormatException e) {
			log.error("转换"+channelId+".proxy.port属性指定的端口值出错，使用默认端口：1080", e);
		}
		SocketAddress addr = new InetSocketAddress(proxyHost, proxyPort);
		return new Proxy(proxyType, addr);
	}

	@Override
	public String toString() {
		return "[" + this.getChannelId() + "," + this + "]";
	}

	/**
	 * @return the retryCountOnSendFailed
	 */
	public int getRetryCountOnSendFailed() {
		return retryCountOnSendFailed;
	}

	/**
	 * @param retryCountOnSendFailed
	 *            the retryCountOnSendFailed to set
	 */
	public void setRetryCountOnSendFailed(int retryCountOnSendFailed) {
		this.retryCountOnSendFailed = retryCountOnSendFailed;
	}

	/**
	 * @return the intervalOnSendFailed
	 */
	public int getIntervalOnSendFailed() {
		return intervalOnSendFailed;
	}

	/**
	 * @param intervalOnSendFailed
	 *            the intervalOnSendFailed to set
	 */
	public void setIntervalOnSendFailed(int intervalOnSendFailed) {
		this.intervalOnSendFailed = intervalOnSendFailed;
	}
}
